#ifndef DSPSIMULATOR_INSTRUCTION_H
#define DSPSIMULATOR_INSTRUCTION_H

#include <string>
#include <memory>
#include <utility>
#include <simulator/targetMachine/targetMachine.h>
#include <simulator/llvmJit/llvmJit.h>
#include <simulator/type/type.h>
#include <simulator/instruction/executePhase.h>
#include <simulator/instruction/instInfo.h>

class Instruction {
protected:
    std::string m_name;  // 指令名
    u64 m_address;       // 指令所在的内存地址
    u64 m_baseCycle;  //  当前指令执行的起始时钟周期
    u64 m_delay; // 延时大小

    /**
     * 有些指令需要在执行前更新全局的状态信息，通过此函数实现该功能
     * @param targetMachine 目标机
     * @param llvmJit llvm jit执行引擎
     */
    virtual void
    updateContextBeforeExecute(std::shared_ptr<TargetMachine> targetMachine, std::shared_ptr<LLVMJit> llvmJit) = 0;

public:

    /**
     * @param name 指令名
     * @param address 指令地址
     * @param baseCycle 当前指令执行E1阶段的时钟周期
     * @param delay 延时大小
     */
    explicit Instruction(const std::string &name, u64 address, u64 baseCycle, u64 delay) :
            m_name(name), m_address(address), m_baseCycle(baseCycle), m_delay(delay) {};

    virtual ~Instruction() = default;

    Instruction(const Instruction &instruction) = default;

    /**
     * 获取指令所在的内存地址
     * @return 指令所在的内存地址
     */
    u64 getInstructionAddress() const { return m_address; }

    /**
     * 获取指令的名称
     * @return 指令名称
     */
    std::string getName() const { return m_name; }

    /**
     * 获取指令执行的起始时钟周期
     * @return 指令执行的起始时钟周期
     */
    u64 getBaseCycle() const { return m_baseCycle; }

    /**
     * 判断当前指令是否为延时指令
     * @return 如果当前指令是延时指令，返回true，否则返回false
     */
    bool isDelayInstruction() const { return m_delay != 0; }

    /**
     * 获取指令的延时
     * @return 指令的延时
     */
    u64 getDelay() const { return m_delay; }

    /**
     * 将指令的ir执行函数加载到llvm jit执行引擎中
     */
    virtual void loadInstIRIntoJit(llvm::LLVMContext *llvmContext, llvm::Module *module,
                                   std::shared_ptr<llvm::IRBuilder<>> irBuilder) = 0;

    /**
     * 根据指令数据解码指令，并返回新的指令对象，该指令对象表示解码完成的指令
     * @param instInfo 指令数据
     * @param baseCycle 指令E1阶段的时钟周期
     * @return
     */
    virtual std::shared_ptr<Instruction> decode(std::shared_ptr<InstInfo> instInfo, u64 baseCycle) = 0;

    /**
     * 执行当前指令
     * @param targetMachine 目标机对象
     * @param llvmJit llvm Jit对象
     * @param executePhase 当前指令的执行阶段（有些指令需要多个周期执行，每个周期的执行过程不一定相同，需要单独处理）
     */
    void execute(std::shared_ptr<TargetMachine> targetMachine, std::shared_ptr<LLVMJit> llvmJit,
                 ExecutePhase executePhase = ExecutePhase::E1) {
        updateContextBeforeExecute(targetMachine, llvmJit);
        executeInJit(llvmJit);
        executeCustom(targetMachine, executePhase);
    };

    /**
     * 通过llvm Jit执行指令
     * @param llvmJit llvm Jit对象
     */
    void executeInJit(std::shared_ptr<LLVMJit> llvmJit) {
        // 根据指令对应的IR函数名获取对应的函数地址
        auto funcAddr = (void *) llvmJit->getExecutionEngine()->getFunctionAddress(getIRFuncName());
        // 如果函数不存在，标识该函数不通过llvm jit执行，直接退出
        if (!funcAddr)
            return;
        // 如果函数存在，通过llvm jit执行该函数
        auto funcPtr = (void (*)()) (funcAddr);
        funcPtr();
    };

    /**
     * 自定义函数执行指令，不通过llvm jit执行指令
     * @param targetMachine 目标机对象
     * @param executePhase 当前指令的执行阶段（有些指令需要多个周期执行，每个周期的执行过程不一定相同，需要单独处理）
     */
    virtual void executeCustom(std::shared_ptr<TargetMachine> targetMachine, ExecutePhase executePhase) = 0;

    /**
     * 获取指令在llvm jit中对应的函数名
     * @return 指令在llvm jit中对应的函数名
     */
    virtual std::string getIRFuncName() const = 0;

    /**
     * 生成标识指令信息的字符串
     * @return 标识指令信息的字符串
     */
    virtual std::string toString() const = 0;

    /**
     * 修改指令的起始时钟周期
     * @param baseCycle  指令的起始时钟周期
     */
    void updateBaseCycle(u64 baseCycle) {
        m_baseCycle = baseCycle;
    }
};

#endif //DSPSIMULATOR_INSTRUCTION_H
